local socket = require "client.socket"
local crypt = require "client.crypt"

local M = {} 

local login_host, login_port
local gate_host, gate_port 
local servername, username, password 
local signup_host, signup_port

local last = ""
local fd 

local function unpack_package(text)
	local size = #text
	if size < 2 then
		return nil, text
	end
	local s = text:byte(1) * 256 + text:byte(2)
	if size < s+2 then
		return nil, text
	end

	return text:sub(3,2+s), text:sub(3+s)
end

local function recv_package(last)
    local result 
    result, last = unpack_package(last) 
    if result then 
        return result, last 
    end 
    local r = socket.recv(fd) 
    if not r then 
        return nil, last
    end 
    if r == "" then 
        error("server closed")
    end 
    return unpack_package(last .. r) 
end 

local function read_package()
    while true do 
        local result 
        result, last = recv_package(last) 

        if result then 
            print("Recv : " .. " [ " .. result .. " ] ")
            return result 
        end 

        socket.usleep(100)
    end 
end 

function M.try_read_package() 
    local result 
    result, last = recv_package(last) 
    if result then 
        return result
    end 
end 

function M.send_package(pack)
    if fd then 
        local package = string.pack(">s2", pack)
        socket.send(fd, package)
    end 
end 

function M.connect_login_server_signup()
    local function encode_token(token)
        return string.format("%s:%s",
            crypt.base64encode(token.user),
            crypt.base64encode(token.pass))
    end

    fd = assert(socket.connect(signup_host, signup_port))

    local token = {
        user = username,
        pass = password,
    }

    M.send_package(encode_token(token))

    local result = read_package()
    local code = tonumber(string.sub(result, 1, 3))

    local resp = {} 
    resp.msg = string.sub(result, 5)

    if code ~= 200 then 
        resp.ok = false 
    else 
        resp.ok = true 
    end 

    socket.close(fd)
    return resp
end 

function M.connect_login_server()
    local function encode_token(token)
        return string.format("%s:%s@%s",
            crypt.base64encode(token.SDK),
            crypt.base64encode(token.user .. '\t' .. token.pass),
            crypt.base64encode(token.server))
    end

    fd = assert(socket.connect(login_host, login_port))

    local token = {
        user = username,
        server = servername,
        pass = password,
        SDK = "skynetchat",
    }

    local challenge = crypt.base64decode(read_package())
    local clientkey = crypt.randomkey()
    M.send_package(crypt.base64encode(crypt.dhexchange(clientkey)))

    local secret = crypt.dhsecret(crypt.base64decode(read_package()), clientkey)
    print("sceret : [ " .. crypt.hexencode(secret) .. " ] ")
    local hmac = crypt.hmac64(challenge, secret)
    M.send_package(crypt.base64encode(hmac))

    local token_encode = crypt.desencode(secret, encode_token(token))
    token_encode = crypt.base64encode(token_encode)
    M.send_package(token_encode)

    local result = read_package()
    local code = tonumber(string.sub(result, 1, 3))

    local resp = {} 

    if code ~= 200 then 
        resp.ok = false 
    else 
        local JS_user_online = crypt.base64decode(string.sub(result, 5))
        local user_online = require "cjson".decode(JS_user_online)
        -- login 服务返回的消息 user_online 

        resp.uid = user_online.uid -- 传输涉及编解码，uid太长，用string类型
        resp.subid = user_online.subid 
        resp.servername = user_online.servername 
        resp.gate_addr = user_online.gate_addr 
        resp.secret = user_online.secret
        resp.ok = true 
    end 
    
    socket.close(fd)

    return resp 
end 

-- auth with gate server 
function M.connect_gate_server(servername, uid, handshake_index, secret)
    fd = assert(socket.connect(gate_host, gate_port))

    -- format uid@base64(servername)#index:base64(hmac) -- msgserver.lua: do_auth
    local handshake = string.format("%d@%s#%d", uid, crypt.base64encode(servername), handshake_index)
    local hmac = crypt.hmac64(crypt.hashkey(handshake), secret)

    local text = handshake .. ":" .. crypt.base64encode(hmac)

    M.send_package(text)

    local result = read_package()
    local code = tonumber(string.sub(result, 1, 3))
    if code == 200 then 
        return true 
    end 
    return false 
end 

function M.set_signup_conf(host, port)
    signup_host = host 
    signup_port = port
end 

function M.set_login_conf(host, port)
    login_host = host 
    login_port = port 
end 

function M.set_gate_conf(host, port)
    gate_host = host 
    gate_port = port
end 

function M.set_credential(server, user, pass)
    servername = server 
    username = user 
    password = pass 
end 

return M 